home *** CD-ROM | disk | FTP | other *** search
/ BCI NET / BCI NET Dec 94.iso / archives / programming / languages / oberon.lha / system / Edit.Programming.Text (.txt) < prev    next >
Oberon Text  |  1994-06-23  |  71KB  |  1,111 lines

  1. Syntax12.Scn.Fnt
  2. ParcElems
  3. Alloc
  4. Syntax14m.Scn.Fnt
  5. Syntax10.Scn.Fnt
  6. Syntax12m.Scn.Fnt
  7. Syntax12i.Scn.Fnt
  8. GraphicElems
  9. Alloc
  10. Syntax10i.Scn.Fnt
  11. Edit Core
  12. Syntax10.Scn.Fnt
  13. EditTools
  14. TextPrinter
  15. TextFrames
  16. Rectangles
  17. Elektra.Scn.Fnt
  18. Elements
  19. Syntax10b.Scn.Fnt
  20. Command Packages
  21. LineElems
  22. ParcElems
  23. Texts
  24. StyleElems
  25. Alloc
  26. begin figure
  27. TableElems
  28. Alloc
  29. Syntax10.Scn.Fnt
  30. ParcElems
  31. Alloc
  32. TableElems
  33. Alloc
  34. Syntax10.Scn.Fnt
  35. ParcElems
  36. Alloc
  37. /nohead "*"/noline "*"/col "LCR"/table
  38.  1 mm = 36'000 units                  1 point = 12'700 units                1 inch = 914'400 units 
  39. Syntax12.Scn.Fnt
  40. Syntax10.Scn.Fnt
  41. ParcElems
  42. Alloc
  43. /top 8/nohead "v"/noline "hvlrb"/col "NNL"/table
  44. dots per inch    units per dot     examples
  45. 91    10'000     Ceres-1/2 monochrome monitors; Ceres *.Scn.Fnt files
  46. 300    3'048     Ceres laser printer; Ceres *.Pr3.Fnt and *.Lm3.Fnt files
  47. /top 8/nohead "*"/noline "hvlrb"/col "C"/table
  48. LineElems
  49. Alloc
  50. Syntax10i.Scn.Fnt
  51. Syntax10b.Scn.Fnt
  52. Math12.Scn.Fnt
  53. Programming Elements for the Oberon Text System
  54. 3.0  (28 Sept 93)
  55. C. Szyperski / M. Hausner
  56. Introduction
  57. This memo gives a tutorial on how to program elements for the Oberon text system. It assumes that the reader has acquired some knowledge on how to use Edit. If this is not the case it is strongly recommended to first read the Edit Guide [1].
  58.     The memo begins with an overview showing the module structure and continues with concise descriptions of the core modules. Thereafter a stepwise introduction to the programming of text elements is given. To avoid confusion it is possible to skip the module descriptions and directly start with the programming tutorial, reading the module descriptions as soon as needed.
  59. Related documents. For a description of available commands in modules Edit and EditTools, refer to the separate memo Edit.Guide.Text [1]. For a list of currently available elements, see Elem.Guide.Text [2]. The original Edit system has been described in [3]. This report is still a useful reference when looking for the design ideas behind Edit ([4] is another, more concise reference). However, many of the details are outdated by now. The Oberon system in general is documented in [5].
  60. [1] Edit.Guide.Text - Memo, Institute for Computer Systems, ETH Z
  61. rich.
  62. [2] Elem.Guide.Text - Memo, Institute for Computer Systems, ETH Z
  63. rich.
  64. [3] Clemens A. Szyperski. Write - An Extensible Text Editor for the Oberon System.
  65.     Technical Report 151, Dep. Informatik, ETH Z
  66. rich, January 1991.
  67. [4] Clemens A. Szyperski. Write_ing Applications. Designing an Extensible Text Editor as an
  68.     Application Framework. Proceedings TOOLS Europe '92, Dortmund, Germany;
  69.     Prentice Hall, Englewood Cliffs, NJ. March 1992.
  70. [5] Martin Reiser. The Oberon System - User Guide and Programmer's Manual.
  71.     Addison_Wesley, Reading, MA. 1991.
  72. Overview
  73. This section introduces the modular structure of Edit and the distribution of abstractions to the various modules. Figure 1 illustrates the import relation of the Edit core modules (TextFrames, TextPrinter, ParcElems, and Texts), and the placement of typical extensions. The simpler type of extensions follows the generic Oberon model of adding new commands by implementing a new command package, i.e. a module exporting the new commands. The more involved type of extension adds new elements to the text system. Both kinds of extensions typically import all of the core modules plus any number of additionally needed other modules. (Module ParcElems is not normally required to implement a command or an element.)
  74. Figure 1. Edit Core Modules and Placement of Typical Extensions.
  75. Texts
  76. Texts are generalized to be understood as sequences of attributed text_objects, where a text object is either an ordinary character or a text element. As for standard texts, the attributes are font, color, and vertical offset.
  77.     Elements behave like normal characters of ASCII code 1CX when read by a Reader or a Scanner. Additionally, the Reader field elem contains a reference to the previously read element, or NIL if a normal character was read last. Hence, programs that are not aware of elements, but are well_behaved when reading a non_graphical character of the ASCII set, can process texts containing elements. For example, a program source containing elements can be compiled, as the compiler considers all non_graphical characters as white space. Hence,
  78.     Texts.Read(R, ch);
  79.     IF R.elem # NIL THEN ...handle element...
  80.     ELSE ...handle normal character...
  81.     END;
  82. can be used to operate on a text with embedded elements.
  83.     An element is characterized by its bounding box (W, H) in device independent coordinates. The used unit is defined as follows:
  84. Pixel Resolution vs. Device Independent Units.
  85. Furthermore, each element has a handler attached by means of field handle. The functionality of handlers may be compared to the frame handlers used in the Oberon viewer system. Its principles are not investigated further in this memo. By means of its handler, an element can react to messages. Texts defines the model_level messages for element filing (FileMsg), element copying (CopyMsg), and element identification (IdentifyMsg).
  86.     New elements can be written to a Buffer using procedure WriteElem. Care must be taken that a particular element is not present in more than one text at more than one position at a time. (This condition is checked and may cause a trap 99 in procedure WriteElem.) The base text of an element may be retrieved using procedure ElemBase. If the element does not belong to a text, NIL is returned. When writing an element to a text that already belongs to a text, it must be copied first. Copying is done by sending a CopyMsg to the element. The copy is returned in the message field e. (Procedure CopyElem is used to implement the copy mechanism of an element. It cannot be used to copy an element directly.)
  87.     Elements can be retrieved from a text by sequential reading (using the standard Read procedure), or by directly reading the next or previous element starting from a certain position in the text. The latter is done by calling ReadElem or ReadPrevElem, respectively.
  88.     Finally, the standard procedures Load (or Open) and Store can be used to load and store texts containing arbitrary elements. Texts uses the identification and filing messages to implement this kind of generic loading and storing. Newly created elements during load_time are transferred to Texts using variable new.
  89.     Modules that implement text elements do not register with Texts: When receiving an identify message, an element returns the module and procedure string identifying an allocation procedure; these strings are dictionary coded and written to the file. This is used upon load time (via Modules) to invoke the allocation procedure which has to allocate an element, install a handler, and assign that element to the global variable Texts.new. If this worked, a load message is sent to the newly created object; otherwise the box information is used (and the remaining element block skipped) to create an alien element. Likewise, a store message is used to ask an element to write its private data.
  90.     Procedure CopyElem copies all fields defined in the base type ElemDesc. It is supposed to be called from copy procedures defined for extended types. (A record assignment is not recommened: it copies private fields, the prev and next pointers in this case.) ElemBase makes use of the fact that elements are firmly integrated by allowing an element to find out about its current host text (or NIL if there is none).
  91. TextFrames
  92. TextFrames implements a frame class capable of displaying formatted texts. TextFrames defines a large number of procedures and implements many services. This section is especially terse in describing TextFrames and the reader is referred to the programming tutorial below, where TextFrames functionality is explained in more detail when needed.
  93. TextFrames contains the screen formatter. It defines a special display prepare message (TextFrames.DisplayMsg with field prepare=TRUE) sent to elements about to be displayed. This allows for adapting to remaining space on a line and the like. Hence, an element is allowed to change its bounding box upon receipt of a display prepare message.
  94.     TextFrames tries to delegate mouse clicks to elements hit by the cursor. This is done by sending a TextFrames.TrackMsg. By tracking the mouse until all mouse buttons have been released again, the element can selectively consume mouse clicks. It is strongly recommended to restrict the set of consumed mouse clicks to middle button clicks and interclicks, i.e. the command click combinations. These are undefined for elements, anyway. Consuming left or right button clicks or interclicks causes interference with the caret and selection controlling clicks interpreted by text frames.
  95.     It is possible to attach elements to some external model, i.e. to use elements as views showing some shared model. Elements doing so may install a sub-frame by allocating a new frame of some appropriate class and assigning it to field TextFrames.DisplayMsg.elemFrame. To update these views, a model change should cause a notification by broadcasting a message to the viewer system. TextFrames delegates such messages to all installed sub-frames. A message derived from TextFrames.NotifyMsg has the additional property that the delegating frame adds its identity to the field TextFrames.NotifyMsg.frame.
  96.     Opening a TextFrame takes besides the handler to be used and the text to be displayed a bunch of additional parameters. For each of these a default variable is exported. The parameters define the border width defined around a displayed text in a text frame (left, right, top, bot), and the scroll bar width (barW, if barW > left the scroll bar is not displayed and not available).
  97. DEFINITION TextFrames;    (* CAS/MH/HM 21.9.1993 (Rel 3.0) *)
  98.     IMPORT
  99.          Display, Fonts, Texts;
  100.     CONST
  101.         (* update message IDs *)
  102.         replace = 0; insert = 1; delete = 2;
  103.         (* units *)
  104.         mm = 36000; Unit = 10000;
  105.         (*parc options*)
  106.             gridAdj = 0; leftAdj = 1; rightAdj = 2; pageBreak = 3; twoColumns = 4;
  107.             (* adjust line height to multiples of the selected line height (optional);
  108.                 adjust paragraphs to the left (default), the right, the center (neither left nor right),
  109.                 or block adjust (left and right);
  110.                 enforce a page break before the parc when printing (optional);
  111.                 format using two columns (optional)
  112.             *)
  113.         MaxTabs = 32;
  114.     TYPE
  115.         Parc = POINTER TO ParcDesc;
  116.         ParcDesc = RECORD (Texts.ElemDesc)
  117.             left: LONGINT;    (* distance from (F.X + F.left); in units *)
  118.             first: LONGINT;    (* first line indentation from P.left; in units *)
  119.             width: LONGINT;    (* parc width; in units *)
  120.             lead: LONGINT;    (* distance to previous line; in units *)
  121.             lsp: LONGINT;    (* line spacing of text after P; in units *)
  122.             dsr: LONGINT;    (* descender of text after P; in units *)
  123.             opts: SET;
  124.             nofTabs: INTEGER;
  125.             tab: ARRAY MaxTabs OF LONGINT;    (* in units *)
  126.         END;
  127.         Location = RECORD
  128.             org, pos: LONGINT;
  129.             x, y, dx, dy: INTEGER
  130.         END;
  131.         Frame = POINTER TO FrameDesc;
  132.         FrameDesc = RECORD (Display.FrameDesc)
  133.             text: Texts.Text;    (*he displayed text*)
  134.             org: LONGINT;    (*position of first character displayed*)
  135.             col: INTEGER;    (*frame background color*)
  136.             left, right, top, bot: INTEGER;    (*frame border, in pixels*)
  137.             markH: INTEGER;    (* position of tick mark in scroll bar (< 0 => no tick mark) *)
  138.             barW: INTEGER;    (*scroll bar width*)
  139.             time: LONGINT;    (* selection time *)
  140.             hasCar, hasSel, showsParcs: BOOLEAN;    (*caret/selection present; parcs visible*)
  141.             carloc, selbeg, selend: Location;
  142.             focus: Display.Frame;     (* frame of nested element if this element contains the focus *)
  143.         END;
  144.         DisplayMsg = RECORD (Texts.ElemMsg)
  145.             prepare: BOOLEAN;
  146.             fnt: Fonts.Font;
  147.             col: SHORTINT;
  148.             pos: LONGINT;    (*position in host text*)
  149.             frame: Display.Frame;    (*~prepare => host frame*)
  150.             X0, Y0: INTEGER;    (*~prepare => receiver origin in screen space*)
  151.             indent: LONGINT;    (*prepare => width already consumed in line, in units*)
  152.             elemFrame: Display.Frame    (*optional return parameter*)
  153.         END;
  154.         TrackMsg = RECORD (Texts.ElemMsg)
  155.             X, Y: INTEGER;
  156.             keys: SET;
  157.             fnt: Fonts.Font;
  158.             col: SHORTINT;
  159.             pos: LONGINT;    (*position in host text*)
  160.             frame: Display.Frame;    (*host frame*)
  161.             X0, Y0: INTEGER    (*receiver origin in screen space*)
  162.         END;
  163.         FocusMsg = RECORD (Texts.ElemMsg)
  164.             focus: BOOLEAN;    (*whether to focus or to defocus*)
  165.             elemFrame: Display.Frame;    (*focus/defocus target*)
  166.             frame: Display.Frame    (*host frame*)
  167.         END;
  168.         NotifyMsg = RECORD (Display.FrameMsg)
  169.             frame: Display.Frame    (*host frame*)
  170.         END;
  171.         UpdateMsg = RECORD (Display.FrameMsg)
  172.             id: INTEGER;    (* replace, insert, delete *)
  173.             text: Texts.Text;
  174.             beg, end: LONGINT
  175.         END;
  176.         InsertElemMsg = RECORD (Display.FrameMsg)
  177.             e: Texts.Elem;
  178.         END;
  179.         left, right, top, bot, barW, menuH: INTEGER;    (*default values used when opening a new frame*)
  180.         defParc: Parc;
  181.     PROCEDURE Mark (F: Frame; mark: INTEGER);
  182.     (* Parcs *)
  183.     PROCEDURE ParcBefore (T: Texts.Text; pos: LONGINT; VAR P: Parc; VAR beg: LONGINT);
  184.         (*retrieve parc P and its position beg responsible for position pos*)
  185.     (* Locators *)
  186.     PROCEDURE LocatePos (F: Frame; pos: LONGINT; VAR loc: Location);
  187.     PROCEDURE LocateLine (F: Frame; y: INTEGER; VAR loc: Location);
  188.     PROCEDURE LocateChar (F: Frame; x, y: INTEGER; VAR loc: Location);
  189.     PROCEDURE LocateWord (F: Frame; x, y: INTEGER; VAR loc: Location);
  190.     PROCEDURE Pos (F: Frame; x, y: INTEGER): LONGINT;
  191.     (* Caret & Selection *)
  192.     PROCEDURE RemoveSelection (F: Frame);
  193.     PROCEDURE SetSelection (F: Frame; beg, end: LONGINT);    (*forces range to visible bounds*)
  194.     PROCEDURE RemoveCaret (F: Frame);
  195.     PROCEDURE SetCaret (F: Frame; pos: LONGINT);    (*only done if within visible bounds*)
  196.     (* Display Range *)
  197.     PROCEDURE Show (F: Frame; pos: LONGINT);
  198.         (*display F.text from pos; adjusts to beginning of line; removes global marks as needed and neutralizes F*)
  199.     (* User Interface *)
  200.     PROCEDURE TrackLine (F: Frame; VAR x, y: INTEGER; VAR org: LONGINT; VAR keysum: SET);
  201.     PROCEDURE TrackWord (F: Frame; VAR x, y: INTEGER; VAR pos: LONGINT; VAR keysum: SET);
  202.     PROCEDURE TrackCaret (F: Frame; VAR x, y: INTEGER; VAR keysum: SET);
  203.     PROCEDURE TrackSelection (F: Frame; VAR x, y: INTEGER; VAR keysum: SET);
  204.     (* General *)
  205.     PROCEDURE Open (F: Frame; T: Texts.Text; pos: LONGINT);
  206.     PROCEDURE Handle (f: Display.Frame; VAR msg: Display.FrameMsg);
  207.     PROCEDURE NotifyDisplay (T: Texts.Text; op: INTEGER; beg, end: LONGINT);
  208.     PROCEDURE Text (name: ARRAY OF CHAR): Texts.Text;
  209.     PROCEDURE NewText (T: Texts.Text; pos: LONGINT): Frame;
  210.     PROCEDURE NewMenu (name, commands: ARRAY OF CHAR): Frame;
  211. END TextFrames.
  212. TextPrinter
  213. TextPrinter provides functionality similar to TextFrames, but supports printing of text portions. The print message has the same fields as the display message, except for passing no frame, and for adding a field pno giving the page number of the page the element is going to be printed on. TextPrinter encapsulates the handling of printer metrics files (*.Lm3.Fnt files): A font can be registered using the procedure FontNo, returning a code number. Font may be used to lookup the font associated with a certain code. Procedure DX(f, c) returns the printer dx of character c using font f. Get(f, c, dx, x, y, w, h) returns the character metrics in global units of c using font f. Finally, GetChar(f, u, c, p, dx, x, y, w, h) returns the metrics for unit u plus the pattern p used for screen output. This last procedure is useful when formatting characters based on printer units in order to display wysiwyg strings on screen. For example, TableElems make use of this to display tables in a wysiwyg fashion.
  214.     Procedure PlaceHeader(x, y, w, pno, f, head, alt) places a header in frame (x, y, w, h), where the hight h depends on the used font f. If alt is false, or pno is odd, the page number pno is placed on the right side of the frame, and the header string head is place on the left side. Otherwise, the page number is placed on the left, and the header string on the right.
  215.     Procedure PlaceBody(x, y, w, h, T, pos, pno, place) formats a page using page number pno to fill the frame (x, y, w, h) starting at pos in text T. If place is true, the page is also sent to the printer. On return, pos indicates the first character that has not fit onto the page, i.e. the first character to appear on the next page.
  216. DEFINITION TextPrinter;    (* CAS 15-Oct-91 *)
  217.     IMPORT
  218.          Display, Fonts, Texts, TextFrames;
  219.     CONST
  220.         Unit = 3048;    (*unit for a 300 dpi printer*)
  221.     TYPE
  222.         PrintMsg = RECORD (Texts.ElemMsg)
  223.             prepare: BOOLEAN;
  224.             indent: LONGINT;    (*prepare => width already consumed in line, in units*)
  225.             fnt: Fonts.Font;
  226.             col: SHORTINT;
  227.             pos: LONGINT;    (*position in host text*)
  228.             X0, Y0, pno: INTEGER    (*receiver origin in screen space; page number*)
  229.         END;
  230.     (* Printer Metrics *)
  231.     PROCEDURE FontNo (fnt: Fonts.Font): SHORTINT;
  232.         (*register font fnt and try to read appropriate Lm3 file if registered for the first time*)
  233.     PROCEDURE Font (fno: SHORTINT): Fonts.Font;
  234.         (*lookup registered font*)
  235.     PROCEDURE DX (fno: SHORTINT; ch: CHAR): LONGINT;
  236.         (*dx in printer metrics*)
  237.     PROCEDURE Get (fno: SHORTINT; ch: CHAR; VAR dx, x, y, w, h: LONGINT);
  238.         (*character box in printer metrics*)
  239.     PROCEDURE GetChar (fno: SHORTINT; targetUnit: LONGINT; ch: CHAR;
  240.                 VAR pdx: LONGINT; VAR dx, x, y, w, h: INTEGER; VAR pat: Display.Pattern);
  241.         (*character box in metrics based on targetUnit, pdx always in global and pat always in screen units*)
  242.     PROCEDURE InitFonts;
  243.         (*to be called from outer Print command, only - releases registered fonts*)
  244.     (* Printer Page Placement *)
  245.     PROCEDURE PlaceHeader (headerX, headerY, headerW: INTEGER;
  246.             pno: INTEGER; fnt: Fonts.Font; VAR header: ARRAY OF CHAR; alt: BOOLEAN);
  247.         (*place a header in  box headerX, headerY, headerW using font fnt*)
  248.     PROCEDURE PlaceBody (bodyX, bodyY, bodyW, bodyH: INTEGER;
  249.             T: Texts.Text; VAR pos: LONGINT; pno: INTEGER; place: BOOLEAN);
  250.         (*format and optionally place page with number pno in box bodyX, bodyY, bodyW, bodyH;
  251.             returns with pos pointing to the first character of the next page or to the end of the text*)
  252.     PROCEDURE PrintDraft (t: Texts.Text; header: ARRAY OF CHAR; copies: INTEGER);
  253. END TextPrinter.
  254. ParcElems
  255. ParcElems implements the parcs (paragraph controls) defined in module TextFrames, adding interactive manipulation features for most parc attributes. Furthermore, the parc handler installed by ParcElems supports querying and changing parc attributes using the message ParcElems.StateMsg. This is used by the commands Edit.Set and Edit.Get. Hence, extended parcs defining new attributes can be defined that in turn extend the parameters taken by Edit.Set and Edit.Get. (The standard StyleElems take advantage of this by propagating changes to all parcs in a text that have the same name.)
  256. DEFINITION ParcElems;    (* CAS 15-Oct-91 *)
  257.     IMPORT
  258.          Display, Files, Texts, TextFrames, TextPrinter;
  259.     CONST
  260.         (*StateMsg.id*)
  261.             set = 0; get = 1;
  262.     TYPE
  263.         StateMsg = RECORD (Texts.ElemMsg)
  264.             id: INTEGER;
  265.             pos: LONGINT;
  266.             frame: TextFrames.Frame;
  267.             par: Texts.Scanner;    (*used to consume set/get arguments*)
  268.             log: Texts.Text    (*used to output set/get protocols*)
  269.         END;
  270.     PROCEDURE ParcExtent (T: Texts.Text; beg: LONGINT; VAR end: LONGINT);
  271.         (*returns influence interval [beg, end) of parc located in T at beg*)
  272.     PROCEDURE ChangedParc (P: TextFrames.Parc; beg: LONGINT);
  273.         (*notify viewers of change in parc P located at beg*)
  274.     PROCEDURE LoadParc (P: TextFrames.Parc; VAR r: Files.Rider);
  275.         (*load parc P using rider r*)
  276.     PROCEDURE StoreParc (P: TextFrames.Parc; VAR r: Files.Rider);
  277.         (*store parc P using rider r*)
  278.     PROCEDURE CopyParc (SP, DP: TextFrames.Parc);
  279.         (*copy source parc SP into destination parc DP*)
  280.     PROCEDURE Prepare (P: TextFrames.Parc; indent, unit: LONGINT);
  281.         (*standard reaction to TextFrames.DisplayMsg(prepare)*)
  282.     PROCEDURE Draw (P: TextFrames.Parc; F: Display.Frame; col: SHORTINT; x0, y0: INTEGER);
  283.         (*standard reaction to TextFrames.DisplayMsg(Xprepare)*)
  284.     PROCEDURE Edit (P: TextFrames.Parc; F: TextFrames.Frame; pos: LONGINT; x0, y0, x, y : INTEGER; keysum: SET);
  285.         (*standard reaction to TextFrames.TrackMsg*)
  286.     PROCEDURE SetAttr (P: TextFrames.Parc; F: TextFrames.Frame; pos: LONGINT;
  287.             VAR S: Texts.Scanner; log: Texts.Text);
  288.         (*use S to scan arguments and set selected attributes of P*)
  289.     PROCEDURE GetAttr (P: TextFrames.Parc; F: TextFrames.Frame; VAR S: Texts.Scanner; log: Texts.Text);
  290.         (*use S to scan arguments and get selected attributes of P*)
  291.     PROCEDURE Handle (E: Texts.Elem; VAR msg: Texts.ElemMsg);
  292.         (*handler of standard parcs and its components*)
  293.     PROCEDURE Alloc;
  294.         (*allocation command called by Texts when loading a parc*)
  295. END ParcElems.
  296. How to Program Text Elements
  297. This section covers the material required to implement new text elements. Starting with almost trivial functionality, new features are added stepwise. Each step is accompanied by a small but functional example in full source form. It is recommended to understand each of the steps before proceeding.
  298. Minimal Element
  299. A minimal element needs to handle the copy message, only. It will be displayed or printed as a white area in the text, visible only indirectly by the place it takes. When storing a text containing a minimal element, the element will not be stored (as it does not handle identification and filing messages). Hence, after reloading the text, the element is gone.
  300.     The implementation consists of only two procedures: A minimal handler to implement copying, and a command to allow elements of the new class to be inserted into texts. The handler uses Texts.CopyElem to implement the copying. If the defined elements would contain any further record fields, these should also be copied when handling a CopyMsg. The insertion procedure creates a new element and initializes the fields W, H, and handle. Then, it sends a TextFrames.InsertElemMsg to the current focus viewer. Note that the implementation of minimal elements does not even require the definition of a new type: As long as no new fields are needed, the type Texts.ElemDesc will do.
  301. 7 Texts.CopyMsg - the receiver creates a fully initialized copy of itself and returns it in the message field e.
  302.     CopyMsg = RECORD (ElemMsg)
  303.         e: Elem
  304.     END;
  305. MODULE MinimalElems;
  306.     IMPORT
  307.         Texts, TextFrames, Oberon;
  308.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  309.         VAR copy: Texts.Elem;
  310.     BEGIN
  311.         IF msg IS Texts.CopyMsg THEN
  312.             NEW(copy); Texts.CopyElem(e, copy); msg(Texts.CopyMsg).e := copy
  313.         END
  314.     END Handle;
  315.     PROCEDURE Insert*;
  316.         VAR e: Texts.Elem; M: TextFrames.InsertElemMsg;
  317.     BEGIN
  318.         NEW(e); e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle;
  319.         M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  320.     END Insert;
  321. END MinimalElems.
  322. Loading and Storing
  323. To load and store elements it is necessary to handle identification messages and to implement an allocator for the defined elements. The identification message informs module Texts about the names of allocator procedure; these names are in turn stored in a text file. When loading a text, Texts uses the names of allocator procedures to call the allocators using Oberon.Call. An allocator in turn creates a new instance of the appropriate element class and assigns it to variable Texts.new.
  324. 7 Texts.IdentifyMsg - the receiver returns the name of its allocator (module, procedure)
  325.     IdentifyMsg = RECORD (ElemMsg)
  326.         mod, proc: ARRAY 32 OF CHAR
  327.     END;
  328. MODULE FileableElems;
  329.     IMPORT
  330.         Texts, TextFrames, Oberon;
  331.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  332.         VAR copy: Texts.Elem;
  333.     BEGIN
  334.         IF msg IS Texts.CopyMsg THEN
  335.             NEW(copy); Texts.CopyElem(e, copy); msg(Texts.CopyMsg).e := copy
  336.         ELSIF msg IS Texts.IdentifyMsg THEN
  337.             WITH msg: Texts.IdentifyMsg DO msg.mod := "FileableElems"; msg.proc := "Alloc" END
  338.         END
  339.     END Handle;
  340.     PROCEDURE Alloc*;
  341.         VAR e: Texts.Elem;
  342.     BEGIN
  343.         NEW(e); e.handle := Handle; Texts.new := e
  344.     END Alloc;
  345.     PROCEDURE Insert*;
  346.         VAR e: Texts.Elem; M: TextFrames.InsertElemMsg;
  347.     BEGIN
  348.         NEW(e); e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle;
  349.         M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  350.     END Insert;
  351. END FileableElems.
  352. Adding a Specific State
  353. The elements implemented so far had no specific state. To add state information to an element, a new type needs to be derived from Texts.ElemDesc. To enable loading and storing of that derived type, handling of another message used for filing elements has to be added. In the example of StatefulElems, a character array has been added to allow the defined elements to hold strings. Insert has been extended to take an argument and use it to initialize the newly created element.
  354. 7 Texts.FileMsg - depending on the message id the receiver loads or stores its state using the rider r. The state of the base type Texts.ElemDesc (W, H) is automatically loaded and stored. (The field pos is the position of the element in the hosting text.)
  355.     FileMsg = RECORD (ElemMsg)
  356.         id: INTEGER;    (*load = 0, store = 1, other values reserved*)
  357.         pos: LONGINT;
  358.         r: Files.Rider
  359.     END;
  360. MODULE StatefulElems;
  361.     IMPORT
  362.         Files, Texts, Oberon, TextFrames;
  363.     TYPE
  364.         Elem = POINTER TO RECORD (Texts.ElemDesc)
  365.             s: ARRAY 32 OF CHAR
  366.         END;
  367.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  368.         VAR copy: Elem; i: INTEGER; ch: CHAR;
  369.     BEGIN
  370.         WITH e: Elem DO
  371.             IF msg IS Texts.CopyMsg THEN
  372.                 NEW(copy); Texts.CopyElem(e, copy); copy.s := e.s;
  373.                 msg(Texts.CopyMsg).e := copy
  374.             ELSIF msg IS Texts.IdentifyMsg THEN
  375.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "StatefulElems"; msg.proc := "Alloc" END
  376.             ELSIF msg IS Texts.FileMsg THEN
  377.                 WITH msg: Texts.FileMsg DO
  378.                     IF msg.id = Texts.load THEN i := 0;
  379.                         REPEAT Files.Read(msg.r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X
  380.                     ELSIF msg.id = Texts.store THEN i := 0;
  381.                         REPEAT ch := e.s[i]; Files.Write(msg.r, ch); INC(i) UNTIL ch = 0X
  382.                     END
  383.                 END
  384.             END
  385.         END
  386.     END Handle;
  387.     PROCEDURE Alloc*;
  388.         VAR e: Elem;
  389.     BEGIN
  390.         NEW(e); e.handle := Handle; Texts.new := e
  391.     END Alloc;
  392.     PROCEDURE Insert*;
  393.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  394.     BEGIN
  395.         Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  396.         IF (s.class = Texts.String) & (s.line = 0) THEN
  397.             NEW(e); e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle;
  398.             COPY(s.s, e.s);
  399.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  400.         END
  401.     END Insert;
  402. END StatefulElems.
  403. Structuring the Implementation
  404. Before proceeding by adding new features some remarks on the recommended structure of element implementing modules may be in order. First of all, it is a good idea to use the handler as a message dispatcher only, i.e. the handling of specific messages should be factored into a set of procedures. This makes the handler far easier to read and the implementation extensible. The latter holds if the handler and the individual handling procedures are exported: An extension implemented in a new module will define a new handler and call the old handler whenever it does not want to intercept the handling of a particular method. If it does intercept a certain message type, the extension often needs to resort to the original implementation, which it does by calling the corresponding handling procedure. Secondly, the initialization of an element should be packaged into an (possibly exported) procedure. Typically, such procedures are named Open... in the Oberon system.
  405.     The module below has exactly the same functionality as the one given in the previous section, but is structured following the conventions just explained. Also, all parts that are required to make the implementation itself extensible have been exported. (Note that for making the element type itself extensible it is necessary to define and export a named record type; this may be compared to the anonymous record type used above.)
  406. MODULE StatefulElems;
  407.     IMPORT
  408.         Files, Texts, Oberon, TextFrames;
  409.     TYPE
  410.         Elem* = POINTER TO ElemDesc;
  411.         ElemDesc* = RECORD (Texts.ElemDesc)
  412.             s*: ARRAY 32 OF CHAR
  413.         END;
  414.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  415.     PROCEDURE Alloc*;
  416.         VAR e: Elem;
  417.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  418.     END Alloc;
  419.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR);
  420.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle; COPY(s, e.s)
  421.     END Open;
  422.     PROCEDURE Copy* (se, de: Elem);
  423.     BEGIN Texts.CopyElem(se, de); de.s := se.s
  424.     END Copy;
  425.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  426.         VAR i: INTEGER; ch: CHAR;
  427.     BEGIN i := 0;
  428.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X
  429.     END Load;
  430.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  431.         VAR i: INTEGER; ch: CHAR;
  432.     BEGIN i := 0;
  433.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X
  434.     END Store;
  435.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  436.         VAR copy: Elem;
  437.     BEGIN
  438.         WITH e: Elem DO
  439.             IF msg IS Texts.CopyMsg THEN
  440.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  441.             ELSIF msg IS Texts.IdentifyMsg THEN
  442.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "StatefulElems"; msg.proc := "Alloc" END
  443.             ELSIF msg IS Texts.FileMsg THEN
  444.                 WITH msg: Texts.FileMsg DO
  445.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  446.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  447.                     END
  448.                 END
  449.             END
  450.         END
  451.     END Handle;
  452.     PROCEDURE Insert*;
  453.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  454.     BEGIN
  455.         Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  456.         IF (s.class = Texts.String) & (s.line = 0) THEN
  457.             NEW(e); Open(e, s.s);
  458.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  459.         END
  460.     END Insert;
  461. END StatefulElems.
  462. Displayable Elements
  463. The elements developed so far are quite unusual as they have no visual representation. Adding visual representation is done by handling another message. This time the message is device specific (corresponds to the display) and hence is defined in module TextFrames instead of Texts. In a first step the visual representation of the element is chosen to be particularly simple: the area taken by the element is simply filled with a grey pattern.
  464. 7 TextFrames.DisplayMsg - If Xprepare, the receiver is asked to display itself (using module Display) at absolute screen coordinates (X0, Y0). The text attributes set for the receiving element are given by fnt and col; if set, a vertical offset is cumulated into coordinate Y0. The field pos is the position of the element in the hosting text. The hosting frame is available via frame. Field indent is not valid when Xprepare holds. Finally, an element may install a subframe by creating a new frame and returning it using field elemFrame. (This is explained in an example further below.)
  465.     DisplayMsg = RECORD (Texts.ElemMsg)
  466.         prepare: BOOLEAN;
  467.         fnt: Fonts.Font;
  468.         col: SHORTINT;
  469.         pos: LONGINT;
  470.         frame: Display.Frame;
  471.         X0, Y0: INTEGER;
  472.         indent: LONGINT;
  473.         elemFrame: Display.Frame
  474.     END;
  475. MODULE VisibleElems;
  476.     IMPORT
  477.         Files, Display, Texts, Oberon, TextFrames;
  478.     TYPE
  479.         Elem* = POINTER TO ElemDesc;
  480.         ElemDesc* = RECORD (Texts.ElemDesc)
  481.             s*: ARRAY 32 OF CHAR
  482.         END;
  483.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  484.     PROCEDURE Alloc*;
  485.         VAR e: Elem;
  486.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  487.     END Alloc;
  488.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR);
  489.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle; COPY(s, e.s)
  490.     END Open;
  491.     PROCEDURE Copy* (se, de: Elem);
  492.     BEGIN Texts.CopyElem(se, de); de.s := se.s
  493.     END Copy;
  494.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  495.         VAR i: INTEGER; ch: CHAR;
  496.     BEGIN i := 0;
  497.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X
  498.     END Load;
  499.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  500.         VAR i: INTEGER; ch: CHAR;
  501.     BEGIN i := 0;
  502.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X
  503.     END Store;
  504.     PROCEDURE Draw* (e: Elem; col, x0, y0: INTEGER);
  505.         VAR w, h: INTEGER;
  506.     BEGIN w := SHORT(e.W DIV TextFrames.Unit); h := SHORT(e.H DIV TextFrames.Unit);
  507.         Display.ReplPattern(col, Display.grey1, x0, y0, w, h, Display.replace)
  508.     END Draw;
  509.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  510.         VAR copy: Elem;
  511.     BEGIN
  512.         WITH e: Elem DO
  513.             IF msg IS Texts.CopyMsg THEN
  514.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  515.             ELSIF msg IS Texts.IdentifyMsg THEN
  516.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "VisibleElems"; msg.proc := "Alloc" END
  517.             ELSIF msg IS Texts.FileMsg THEN
  518.                 WITH msg: Texts.FileMsg DO
  519.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  520.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  521.                     END
  522.                 END
  523.             ELSIF msg IS TextFrames.DisplayMsg THEN
  524.                 WITH msg: TextFrames.DisplayMsg DO
  525.                     IF ~msg.prepare THEN Draw(e, msg.col, msg.X0, msg.Y0) END
  526.                 END
  527.             END
  528.         END
  529.     END Handle;
  530.     PROCEDURE Insert*;
  531.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  532.     BEGIN
  533.         Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  534.         IF (s.class = Texts.String) & (s.line = 0) THEN
  535.             NEW(e); Open(e, s.s);
  536.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  537.         END
  538.     END Insert;
  539. END VisibleElems.
  540. Printable Elements
  541. Besides displaying an element it is useful to have a printed representation. To implement priniting it is necessary to handle the print message defined by module TextPrinter. It is very similar to the display message introduced above, but contains fewer fields, as certain features designed for interaction (like subframes) make no sense when printing. Also, the print message assumes that the receiving element uses modules Printer (and perhaps TextPrinter) instead of Display to output its representation. The example below uses a grey pattern to print itself that it similar to the one used for displaying.
  542. 7 TextPrinter.PrintMsg - If Xprepare, the receiver is asked to print itself (using module Printer) at absolute printer coordinates (X0, Y0). The text attributes set for the receiving element are given by fnt and col; if set, a vertical offset is cumulated into coordinate Y0. The field pos is the position of the element in the hosting text. Field indent is not valid when Xprepare holds. The number of the page that is used for the page the element will be printed on is indicated by pno.
  543.     DisplayMsg = RECORD (Texts.ElemMsg)
  544.         prepare: BOOLEAN;
  545.         indent: LONGINT;
  546.         fnt: Fonts.Font;
  547.         col: SHORTINT;
  548.         pos: LONGINT;
  549.         X0, Y0, pno: INTEGER
  550.     END;
  551. MODULE PrintableElems;
  552.     IMPORT
  553.         Files, Display, Texts, Oberon, Printer, TextFrames;
  554.     TYPE
  555.         Elem* = POINTER TO ElemDesc;
  556.         ElemDesc* = RECORD (Texts.ElemDesc)
  557.             s*: ARRAY 32 OF CHAR
  558.         END;
  559.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  560.     PROCEDURE Alloc*;
  561.         VAR e: Elem;
  562.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  563.     END Alloc;
  564.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR);
  565.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle; COPY(s, e.s)
  566.     END Open;
  567.     PROCEDURE Copy* (se, de: Elem);
  568.     BEGIN Texts.CopyElem(se, de); de.s := se.s
  569.     END Copy;
  570.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  571.         VAR i: INTEGER; ch: CHAR;
  572.     BEGIN i := 0;
  573.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X
  574.     END Load;
  575.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  576.         VAR i: INTEGER; ch: CHAR;
  577.     BEGIN i := 0;
  578.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X
  579.     END Store;
  580.     PROCEDURE Draw* (e: Elem; col, x0, y0: INTEGER);
  581.         VAR w, h: INTEGER;
  582.     BEGIN w := SHORT(e.W DIV TextFrames.Unit); h := SHORT(e.H DIV TextFrames.Unit);
  583.         Display.ReplPattern(col, Display.grey1, x0, y0, w, h, Display.replace)
  584.     END Draw;
  585.     PROCEDURE Print* (e: Elem; col, x0, y0: INTEGER);
  586.         VAR w, h: INTEGER;
  587.     BEGIN w := SHORT(e.W DIV TextPrinter.Unit); h := SHORT(e.H DIV TextPrinter.Unit);
  588.         Printer.ReplPattern(x0, y0, w, h, 2)
  589.     END Print;
  590.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  591.         VAR copy: Elem;
  592.     BEGIN
  593.         WITH e: Elem DO
  594.             IF msg IS Texts.CopyMsg THEN
  595.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  596.             ELSIF msg IS Texts.IdentifyMsg THEN
  597.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "PrintableElems"; msg.proc := "Alloc" END
  598.             ELSIF msg IS Texts.FileMsg THEN
  599.                 WITH msg: Texts.FileMsg DO
  600.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  601.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  602.                     END
  603.                 END
  604.             ELSIF msg IS TextFrames.DisplayMsg THEN
  605.                 WITH msg: TextFrames.DisplayMsg DO
  606.                     IF ~msg.prepare THEN Draw(e, msg.col, msg.X0, msg.Y0) END
  607.                 END
  608.             ELSIF msg IS TextPrinter.PrintMsg THEN
  609.                 WITH msg: TextPrinter.PrintMsg DO
  610.                     IF ~msg.prepare THEN Print(e, msg.col, msg.X0, msg.Y0) END
  611.                 END
  612.             END
  613.         END
  614.     END Handle;
  615.     PROCEDURE Insert*;
  616.         VAR e: Elem; s: Texts.Scanner;  M: TextFrames.InsertElemMsg;
  617.     BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  618.         IF (s.class = Texts.String) & (s.line = 0) THEN
  619.             NEW(e); Open(e, s.s);
  620.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  621.         END
  622.     END Insert;
  623. END PrintableElems.
  624. Adapting to the Environment
  625. An element may compute its bounding box instead of having it fixed. This can be used to have a printed form that has different size than the displayed form. For example, an element may be visible on screen, but invisible on paper by having a printed form of zero width and height. Also, an element may decide to fill the remaining space in a line, or all space to the next tabulator stop. All these cases are handled by implementing special reactions to the TextFrames.DisplayMsg and TextPrinter.PrintMsg when the prepare flag is set.
  626.     The example below handles the case where the width of the element is different when printing it. The Draw and Print procedures have been changed to display the string hold by the element in an underlined fashion. To do so, the procedures StrDispWidth and StrPrntWidth have been added which use information provided by modules Display and TextPrinter to compute the width of a string when displayed or printed, respectively. (Note that the width of an element is reset after printing it. This avoids problems with tools directly working on a displayed text, as such tools may inspect and use the width stored in field W of an element.)
  627. 7 TextFrames.DisplayMsg - If prepare, the receiver is asked to prepare itself for being displayed. The text attributes set for the receiving element are given by fnt and col; if set, a vertical offset is cumulated into coordinate Y0. The field pos is the position of the element in the hosting text. Field indent indicates the space (in units) already taken in the currently casted line. Field Y0 is set to the base line offset that will be applied to the element. (This value may be changed by the element to affect the resulting base line offset and line heights.) Fields frame, X0 and elemFrame are undefined if prepare holds. Note that the prepare message may be received more than once before the element receives the correspoding display message. This is due to line breaks caused when trying to place the element.
  628.     DisplayMsg = RECORD (Texts.ElemMsg)
  629.         prepare: BOOLEAN;
  630.         fnt: Fonts.Font;
  631.         col: SHORTINT;
  632.         pos: LONGINT;
  633.         frame: TextFrames.Frame;
  634.         X0, Y0: INTEGER;
  635.         indent: LONGINT;
  636.         elemFrame: Display.Frame
  637.     END;
  638. 7 TextPrinter.PrintMsg - If prepare, the receiver is asked to prepare itself for being printed. The text attributes set for the receiving element are given by fnt and col; if set, a vertical offset is cumulated into coordinate Y0. The field pos is the position of the element in the hosting text. Field indent indicates the space (in units) already taken in the currently casted line. Field Y0 is set to the base line offset that will be applied to the element. (This value may be changed by the element to affect the resulting base line offset and line heights.) The number of the page that the element is expected to be printed on is indicated by pno. Field X0 is undefined if prepare holds. Note that the prepare message may be received more than once before the element receives the correspoding print message. This is due to line and page breaks caused when trying to place the element.
  639.     DisplayMsg = RECORD (Texts.ElemMsg)
  640.         prepare: BOOLEAN;
  641.         indent: LONGINT;
  642.         fnt: Fonts.Font;
  643.         col: SHORTINT;
  644.         pos: LONGINT;
  645.         X0, Y0, pno: INTEGER
  646.     END;
  647. MODULE UnderlineElems0;
  648.     IMPORT
  649.         Files, Display, Fonts, Texts, Oberon, Printer, TextFrames, TextPrinter;
  650.     TYPE
  651.         Elem* = POINTER TO ElemDesc;
  652.         ElemDesc* = RECORD (Texts.ElemDesc)
  653.             s*: ARRAY 32 OF CHAR
  654.         END;
  655.     PROCEDURE StrDispWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  656.         VAR pat: Display.Pattern; width, i, dx, x, y, w, h: INTEGER; ch: CHAR;
  657.     BEGIN width := 0;
  658.         i := 0; ch := s[i];
  659.         WHILE ch # 0X DO
  660.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat); INC(width, dx);
  661.             INC(i); ch := s[i]
  662.         END;
  663.         RETURN LONG(width) * TextFrames.Unit
  664.     END StrDispWidth;
  665.     PROCEDURE DispStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; col, x0, y0: INTEGER);
  666.         VAR pat: Display.Pattern; i, dx, x, y, w, h: INTEGER; ch: CHAR;
  667.     BEGIN i := 0; ch := s[i];
  668.         WHILE ch # 0X DO
  669.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat);
  670.             Display.CopyPattern(col, pat, x0+x, y0+y, Display.replace);
  671.             INC(i); ch := s[i]; INC(x0, dx)
  672.         END
  673.     END DispStr;
  674.     PROCEDURE StrPrntWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  675.         VAR width, dx, x, y, w, h: LONGINT; i: INTEGER; fno: SHORTINT; ch: CHAR;
  676.     BEGIN width := 0; fno := TextPrinter.FontNo(fnt);
  677.         i := 0; ch := s[i];
  678.         WHILE ch # 0X DO
  679.             TextPrinter.Get(fno, ch, dx, x, y, w, h); INC(width, dx);
  680.             INC(i); ch := s[i]
  681.         END;
  682.         RETURN width
  683.     END StrPrntWidth;
  684.     PROCEDURE PrntStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; x0, y0: INTEGER);
  685.     BEGIN Printer.String(x0, y0, s, fnt.name)
  686.     END PrntStr;
  687.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  688.     PROCEDURE Alloc*;
  689.         VAR e: Elem;
  690.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  691.     END Alloc;
  692.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR);
  693.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle; COPY(s, e.s)
  694.     END Open;
  695.     PROCEDURE Copy* (se, de: Elem);
  696.     BEGIN Texts.CopyElem(se, de); de.s := se.s
  697.     END Copy;
  698.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  699.         VAR i: INTEGER; ch: CHAR;
  700.     BEGIN i := 0;
  701.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X
  702.     END Load;
  703.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  704.         VAR i: INTEGER; ch: CHAR;
  705.     BEGIN i := 0;
  706.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X
  707.     END Store;
  708.     PROCEDURE PrepDraw* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  709.     BEGIN e.W := StrDispWidth(fnt, e.s); e.H := LONG(fnt.height) * TextFrames.Unit;
  710.         dy := fnt.minY;
  711.         IF dy > -2 THEN dy := -2 END
  712.     END PrepDraw;
  713.     PROCEDURE Draw* (e: Elem; pos: LONGINT; fnt: Fonts.Font; col, x0, y0: INTEGER);
  714.         VAR p: TextFrames.Parc; beg: LONGINT; w: INTEGER;
  715.     BEGIN w := SHORT(e.W DIV TextFrames.Unit);
  716.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  717.         INC(y0, SHORT(p.dsr DIV TextFrames.Unit));
  718.         DispStr(fnt, e.s, col, x0, y0); Display.ReplConst(col, x0, y0 - 2, w, 1, Display.replace)
  719.     END Draw;
  720.     PROCEDURE PrepPrint* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  721.     BEGIN
  722.         e.W := StrPrntWidth(fnt, e.s); e.H := LONG(fnt.height) * TextPrinter.Unit;
  723.         dy := SHORT(LONG(fnt.minY) * TextFrames.Unit DIV TextPrinter.Unit);
  724.         IF dy > -2 THEN dy := -2 END
  725.     END PrepPrint;
  726.     PROCEDURE Print* (e: Elem; pos: LONGINT; fnt: Fonts.Font; x0, y0: INTEGER);
  727.         VAR p: TextFrames.Parc; beg: LONGINT; w: INTEGER;
  728.     BEGIN
  729.         w := SHORT(e.W DIV TextPrinter.Unit);
  730.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  731.         INC(y0, SHORT(p.dsr DIV TextPrinter.Unit));
  732.         PrntStr(fnt, e.s, x0, y0); Printer.ReplConst(x0, y0 - 2, w, 1);
  733.         e.W := StrDispWidth(fnt, e.s)
  734.     END Print;
  735.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  736.         VAR copy: Elem;
  737.     BEGIN
  738.         WITH e: Elem DO
  739.             IF msg IS Texts.CopyMsg THEN
  740.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  741.             ELSIF msg IS Texts.IdentifyMsg THEN
  742.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "UnderlineElems0"; msg.proc := "Alloc" END
  743.             ELSIF msg IS Texts.FileMsg THEN
  744.                 WITH msg: Texts.FileMsg DO
  745.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  746.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  747.                     END
  748.                 END
  749.             ELSIF msg IS TextFrames.DisplayMsg THEN
  750.                 WITH msg: TextFrames.DisplayMsg DO
  751.                     IF msg.prepare THEN PrepDraw(e, msg.fnt, msg.Y0)
  752.                     ELSE Draw(e, msg.pos, msg.fnt, msg.col, msg.X0, msg.Y0)
  753.                     END
  754.                 END
  755.             ELSIF msg IS TextPrinter.PrintMsg THEN
  756.                 WITH msg: TextPrinter.PrintMsg DO
  757.                     IF msg.prepare THEN PrepPrint(e, msg.fnt, msg.Y0)
  758.                     ELSE Print(e, msg.pos, msg.fnt, msg.X0, msg.Y0)
  759.                     END
  760.                 END
  761.             END
  762.         END
  763.     END Handle;
  764.     PROCEDURE Insert*;
  765.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  766.     BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  767.         IF (s.class = Texts.String) & (s.line = 0) THEN
  768.             NEW(e); Open(e, s.s);
  769.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  770.         END
  771.     END Insert;
  772. END UnderlineElems0.
  773. Dealing with Mouse Clicks
  774. The UnderlineElems developed above are already quite useful and well behaved elements. In a next step the elements are refined to react to mouse clicks. To do so, it is sufficient to handle the tracking message defined in TextFrames. The example below interprets a middle mouse click to toggle the state of the element between underlined and normal display of the encapsulated string.
  775. 7 TextFrames.TrackMsg - The receiving element, based at screen coordinates (X0, Y0) is asked to handle a mouse click at screen coordinate (X, Y) with keys pressed. The text attributes set for the receiving element are given by fnt and col; if set, a vertical offset is cumulated into coordinate Y0. The field pos is the position of the element in the hosting text. The hosting frame can be referred to via frame.
  776.     TrackMsg = RECORD (Texts.ElemMsg)
  777.         X, Y: INTEGER;
  778.         keys: SET;
  779.         fnt: Fonts.Font;
  780.         col: SHORTINT;
  781.         pos: LONGINT;
  782.         frame: Display.Frame;
  783.         X0, Y0: INTEGER;
  784.     END;
  785. MODULE UnderlineElems;
  786.     IMPORT
  787.         Files, Input, Display, Fonts, Texts, Oberon, Printer, TextFrames, TextPrinter;
  788.     TYPE
  789.         Elem* = POINTER TO ElemDesc;
  790.         ElemDesc* = RECORD (Texts.ElemDesc)
  791.             s*: ARRAY 32 OF CHAR;
  792.             uline*: BOOLEAN
  793.         END;
  794.     PROCEDURE StrDispWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  795.         VAR pat: Display.Pattern; width, i, dx, x, y, w, h: INTEGER; ch: CHAR;
  796.     BEGIN width := 0;
  797.         i := 0; ch := s[i];
  798.         WHILE ch # 0X DO
  799.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat); INC(width, dx);
  800.             INC(i); ch := s[i]
  801.         END;
  802.         RETURN LONG(width) * TextFrames.Unit
  803.     END StrDispWidth;
  804.     PROCEDURE DispStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; col, x0, y0: INTEGER);
  805.         VAR pat: Display.Pattern; i, dx, x, y, w, h: INTEGER; ch: CHAR;
  806.     BEGIN i := 0; ch := s[i];
  807.         WHILE ch # 0X DO
  808.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat);
  809.             Display.CopyPattern(col, pat, x0+x, y0+y, Display.replace);
  810.             INC(i); ch := s[i]; INC(x0, dx)
  811.         END
  812.     END DispStr;
  813.     PROCEDURE StrPrntWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  814.         VAR width, dx, x, y, w, h: LONGINT; i: INTEGER; fno: SHORTINT; ch: CHAR;
  815.     BEGIN width := 0; fno := TextPrinter.FontNo(fnt);
  816.         i := 0; ch := s[i];
  817.         WHILE ch # 0X DO
  818.             TextPrinter.Get(fno, ch, dx, x, y, w, h); INC(width, dx);
  819.             INC(i); ch := s[i]
  820.         END;
  821.         RETURN width
  822.     END StrPrntWidth;
  823.     PROCEDURE PrntStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; x0, y0: INTEGER);
  824.     BEGIN Printer.String(x0, y0, s, fnt.name)
  825.     END PrntStr;
  826.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  827.     PROCEDURE Alloc*;
  828.         VAR e: Elem;
  829.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  830.     END Alloc;
  831.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR; uline: BOOLEAN);
  832.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle;
  833.         COPY(s, e.s); e.uline := uline
  834.     END Open;
  835.     PROCEDURE Copy* (se, de: Elem);
  836.     BEGIN Texts.CopyElem(se, de); de.s := se.s; de.uline := se.uline
  837.     END Copy;
  838.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  839.         VAR i: INTEGER; ch: CHAR;
  840.     BEGIN i := 0;
  841.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X;
  842.         Files.Read(r, ch);
  843.         IF ch = 0X THEN e.uline := FALSE ELSE e.uline := TRUE END
  844.     END Load;
  845.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  846.         VAR i: INTEGER; ch: CHAR;
  847.     BEGIN i := 0;
  848.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X;
  849.         IF e.uline THEN Files.Write(r, 1X) ELSE Files.Write(r, 0X) END
  850.     END Store;
  851.     PROCEDURE Changed* (e: Elem; pos: LONGINT);
  852.     BEGIN Texts.ChangeLooks(Texts.ElemBase(e), pos, pos+1, {}, NIL, 0, 0)
  853.     END Changed;
  854.     PROCEDURE PrepDraw* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  855.     BEGIN e.W := StrDispWidth(fnt, e.s); e.H := LONG(fnt.height) * TextFrames.Unit;
  856.         dy := fnt.minY;
  857.         IF dy > -2 THEN dy := -2 END
  858.     END PrepDraw;
  859.     PROCEDURE Draw* (e: Elem; pos: LONGINT; fnt: Fonts.Font; col, x0, y0: INTEGER);
  860.         VAR p: TextFrames.Parc; beg: LONGINT; w: INTEGER;
  861.     BEGIN
  862.         w := SHORT(e.W DIV TextFrames.Unit);
  863.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  864.         INC(y0, SHORT(p.dsr DIV TextFrames.Unit));
  865.         DispStr(fnt, e.s, col, x0, y0);
  866.         IF e.uline THEN Display.ReplConst(col, x0, y0 - 2, w, 1, Display.replace) END
  867.     END Draw;
  868.     PROCEDURE PrepPrint* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  869.     BEGIN
  870.         e.W := StrPrntWidth(fnt, e.s); e.H := LONG(fnt.height) * TextPrinter.Unit;
  871.         dy := SHORT(LONG(fnt.minY) * TextFrames.Unit DIV TextPrinter.Unit);
  872.         IF dy > -2 THEN dy := -2 END
  873.     END PrepPrint;
  874.     PROCEDURE Print* (e: Elem; pos: LONGINT; fnt: Fonts.Font; x0, y0: INTEGER);
  875.         VAR p: TextFrames.Parc; beg: LONGINT; w: INTEGER;
  876.     BEGIN
  877.         w := SHORT(e.W DIV TextPrinter.Unit);
  878.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  879.         INC(y0, SHORT(p.dsr DIV TextPrinter.Unit));
  880.         PrntStr(fnt, e.s, x0, y0);
  881.         IF e.uline THEN Printer.ReplConst(x0, y0 - 2, w, 1) END;
  882.         e.W := StrDispWidth(fnt, e.s)
  883.     END Print;
  884.     PROCEDURE Track* (e: Elem; pos: LONGINT; x, y: INTEGER; keys: SET);
  885.         VAR keysum: SET;
  886.     BEGIN
  887.         IF keys = {1} THEN keysum := keys;
  888.             REPEAT Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y);
  889.                 Input.Mouse(keys, x, y); keysum := keysum + keys
  890.             UNTIL keys = {};
  891.             IF keysum = {1} THEN e.uline := ~e.uline; Changed(e, pos) END
  892.         END
  893.     END Track;
  894.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  895.         VAR copy: Elem;
  896.     BEGIN
  897.         WITH e: Elem DO
  898.             IF msg IS Texts.CopyMsg THEN
  899.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  900.             ELSIF msg IS Texts.IdentifyMsg THEN
  901.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "UnderlineElems"; msg.proc := "Alloc" END
  902.             ELSIF msg IS Texts.FileMsg THEN
  903.                 WITH msg: Texts.FileMsg DO
  904.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  905.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  906.                     END
  907.                 END
  908.             ELSIF msg IS TextFrames.DisplayMsg THEN
  909.                 WITH msg: TextFrames.DisplayMsg DO
  910.                     IF msg.prepare THEN PrepDraw(e, msg.fnt, msg.Y0)
  911.                     ELSE Draw(e, msg.pos, msg.fnt, msg.col, msg.X0, msg.Y0)
  912.                     END
  913.                 END
  914.             ELSIF msg IS TextPrinter.PrintMsg THEN
  915.                 WITH msg: TextPrinter.PrintMsg DO
  916.                     IF msg.prepare THEN PrepPrint(e, msg.fnt, msg.Y0)
  917.                     ELSE Print(e, msg.pos, msg.fnt, msg.X0, msg.Y0)
  918.                     END
  919.                 END
  920.             ELSIF msg IS TextFrames.TrackMsg THEN
  921.                 WITH msg: TextFrames.TrackMsg DO Track(e, msg.pos, msg.X, msg.Y, msg.keys) END
  922.             END
  923.         END
  924.     END Handle;
  925.     PROCEDURE Insert*;
  926.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  927.     BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  928.         IF (s.class = Texts.String) & (s.line = 0) THEN
  929.             NEW(e); Open(e, s.s, TRUE);
  930.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  931.         END
  932.     END Insert;
  933. END UnderlineElems.
  934. Inplace Editing and Active Elements
  935. The final refinement of UnderlineElems introduces inplace editing, i.e. the capability to edit the contents of an element in situ. To do so in a way that follows the Oberon model, an element can install a subframe into its hosting text frame. This is done by extending the element's reaction to a DisplayMsg. Each time a display message is received the element creates a new frame, sets it up properly, and returns it in message field elemFrame. This field is initialized to NIL. If the sending text frame finds it to be non_NIL on return, it installs the passed frame into its descender list of frames. Then the user may choose to use a left mouse click to focus one of the installed subframes. A focussed subframe is framed using a grey pattern to make the focus state clearly visible. To control the process of focussing and de_focussing, an element can interpret the TextFrames.FocusMsg.
  936.     A focussed subframe receives all messages broadcasted to the viewer system or sent to the hosting text frame, unless intercepted by the text frame. Hence, any existing frame implementation can be used. (Note: TextFrames currently does not support nesting - hence a text frame currently cannot be installed as a subframe.) Also, a minimalistic frame holding only a very simple handler can be used to implement elements that need to receive broadcast messages when visible. This can be used to implement active elements by having a task broadcast a special message, say once a second. All visible elements that have installed a subframe will receive this message, and - if they know the message type - can react to it. For example, the ClockElems and IconElems modules work this way. The example below uses a minimal frame to receive broadcast messages for simple animation purposes.
  937. 7 TextFrames.FocusMsg - The receiver's subframe is about to be focussed or defocussed, depending on the value of focus. The subframe is indicated by elemFrame, the host frame by frame.
  938.     FocusMsg = RECORD (Texts.ElemMsg)
  939.         focus: BOOLEAN;
  940.         elemFrame: Display.Frame;
  941.         frame: Display.Frame;
  942.     END;
  943. MODULE FancyElems;
  944.     IMPORT
  945.         Files, Input, Display, Viewers, Fonts, Texts, Oberon, Printer, TextFrames, TextPrinter;
  946.     TYPE
  947.         Elem* = POINTER TO ElemDesc;
  948.         ElemDesc* = RECORD (Texts.ElemDesc)
  949.             s*: ARRAY 32 OF CHAR;
  950.             uline*: BOOLEAN
  951.         END;
  952.         TickMsg = RECORD (Display.FrameMsg) END;
  953.         next: LONGINT;
  954.         task: Oberon.Task;
  955.     PROCEDURE Ticker;
  956.         VAR msg: TickMsg;
  957.     BEGIN
  958.         IF Oberon.Time() > next THEN next := Oberon.Time() + 100; Viewers.Broadcast(msg) END
  959.     END Ticker;
  960.     PROCEDURE HandleFrame (f: Display.Frame; VAR msg: Display.FrameMsg);
  961.     BEGIN
  962.         IF msg IS TickMsg THEN Display.ReplConst(Display.white, f.X, f.Y, f.W, f.H, Display.invert)
  963.         ELSIF msg IS Oberon.InputMsg THEN
  964.             WITH msg: Oberon.InputMsg DO
  965.                 IF msg.id = Oberon.track THEN Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, msg.X, msg.Y) END
  966.             END
  967.         END
  968.     END HandleFrame;
  969.     PROCEDURE StrDispWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  970.         VAR pat: Display.Pattern; width, i, dx, x, y, w, h: INTEGER; ch: CHAR;
  971.     BEGIN width := 0;
  972.         i := 0; ch := s[i];
  973.         WHILE ch # 0X DO
  974.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat); INC(width, dx);
  975.             INC(i); ch := s[i]
  976.         END;
  977.         RETURN LONG(width) * TextFrames.Unit
  978.     END StrDispWidth;
  979.     PROCEDURE DispStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; col, x0, y0: INTEGER);
  980.         VAR pat: Display.Pattern; i, dx, x, y, w, h: INTEGER; ch: CHAR;
  981.     BEGIN i := 0; ch := s[i];
  982.         WHILE ch # 0X DO
  983.             Display.GetChar(fnt.raster, ch, dx, x, y, w, h, pat);
  984.             Display.CopyPattern(col, pat, x0+x, y0+y, Display.replace);
  985.             INC(i); ch := s[i]; INC(x0, dx)
  986.         END
  987.     END DispStr;
  988.     PROCEDURE StrPrntWidth* (fnt: Fonts.Font; s: ARRAY OF CHAR): LONGINT;
  989.         VAR width, dx, x, y, w, h: LONGINT; i: INTEGER; fno: SHORTINT; ch: CHAR;
  990.     BEGIN width := 0; fno := TextPrinter.FontNo(fnt);
  991.         i := 0; ch := s[i];
  992.         WHILE ch # 0X DO
  993.             TextPrinter.Get(fno, ch, dx, x, y, w, h); INC(width, dx);
  994.             INC(i); ch := s[i]
  995.         END;
  996.         RETURN width
  997.     END StrPrntWidth;
  998.     PROCEDURE PrntStr* (fnt: Fonts.Font; s: ARRAY OF CHAR; x0, y0: INTEGER);
  999.     BEGIN Printer.String(x0, y0, s, fnt.name)
  1000.     END PrntStr;
  1001.     PROCEDURE^ Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  1002.     PROCEDURE Alloc*;
  1003.         VAR e: Elem;
  1004.     BEGIN NEW(e); e.handle := Handle; Texts.new := e
  1005.     END Alloc;
  1006.     PROCEDURE Open* (e: Elem; s: ARRAY OF CHAR; uline: BOOLEAN);
  1007.     BEGIN e.W := 5*TextFrames.mm; e.H := e.W; e.handle := Handle;
  1008.         COPY(s, e.s); e.uline := uline
  1009.     END Open;
  1010.     PROCEDURE Copy* (se, de: Elem);
  1011.     BEGIN Texts.CopyElem(se, de); de.s := se.s; de.uline := se.uline
  1012.     END Copy;
  1013.     PROCEDURE Load* (e: Elem; VAR r: Files.Rider);
  1014.         VAR i: INTEGER; ch: CHAR;
  1015.     BEGIN i := 0;
  1016.         REPEAT Files.Read(r, ch); e.s[i] := ch; INC(i) UNTIL ch = 0X;
  1017.         Files.Read(r, ch);
  1018.         IF ch = 0X THEN e.uline := FALSE ELSE e.uline := TRUE END
  1019.     END Load;
  1020.     PROCEDURE Store* (e: Elem; VAR r: Files.Rider);
  1021.         VAR i: INTEGER; ch: CHAR;
  1022.     BEGIN i := 0;
  1023.         REPEAT ch := e.s[i]; Files.Write(r, ch); INC(i) UNTIL ch = 0X;
  1024.         IF e.uline THEN Files.Write(r, 1X) ELSE Files.Write(r, 0X) END
  1025.     END Store;
  1026.     PROCEDURE Changed* (e: Elem; pos: LONGINT);
  1027.     BEGIN Texts.ChangeLooks(Texts.ElemBase(e), pos, pos+1, {}, NIL, 0, 0)
  1028.     END Changed;
  1029.     PROCEDURE PrepDraw* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  1030.     BEGIN e.W := StrDispWidth(fnt, e.s); e.H := LONG(fnt.height) * TextFrames.Unit;
  1031.         dy := fnt.minY;
  1032.         IF dy > -2 THEN dy := -2 END
  1033.     END PrepDraw;
  1034.     PROCEDURE Draw* (e: Elem; pos: LONGINT; fnt: Fonts.Font; col, x0, y0: INTEGER; VAR ef: Display.Frame);
  1035.         VAR f: Display.Frame; p: TextFrames.Parc; beg: LONGINT; y, w, h: INTEGER;
  1036.     BEGIN w := SHORT(e.W DIV TextFrames.Unit); h := SHORT(e.H DIV TextFrames.Unit);
  1037.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  1038.         y := y0 + SHORT(p.dsr DIV TextFrames.Unit);
  1039.         DispStr(fnt, e.s, col, x0, y);
  1040.         IF e.uline THEN Display.ReplConst(col, x0, y - 2, w, 1, Display.replace) END;
  1041.         NEW(f); f.X := x0; f.Y := y0; f.W := w; f.H := h; f.handle := HandleFrame;
  1042.         ef := f
  1043.     END Draw;
  1044.     PROCEDURE PrepPrint* (e: Elem; fnt: Fonts.Font; VAR dy: INTEGER);
  1045.     BEGIN e.W := StrPrntWidth(fnt, e.s); e.H := LONG(fnt.height) * TextPrinter.Unit;
  1046.         dy := SHORT(LONG(fnt.minY) * TextFrames.Unit DIV TextPrinter.Unit);
  1047.         IF dy > -2 THEN dy := -2 END
  1048.     END PrepPrint;
  1049.     PROCEDURE Print* (e: Elem; pos: LONGINT; fnt: Fonts.Font; x0, y0: INTEGER);
  1050.         VAR p: TextFrames.Parc; beg: LONGINT; w: INTEGER;
  1051.     BEGIN w := SHORT(e.W DIV TextPrinter.Unit);
  1052.         TextFrames.ParcBefore(Texts.ElemBase(e), pos, p, beg);
  1053.         INC(y0, SHORT(p.dsr DIV TextPrinter.Unit));
  1054.         PrntStr(fnt, e.s, x0, y0);
  1055.         IF e.uline THEN Printer.ReplConst(x0, y0 - 2, w, 1) END;
  1056.         e.W := StrDispWidth(fnt, e.s)
  1057.     END Print;
  1058.     PROCEDURE Track* (e: Elem; pos: LONGINT; x, y: INTEGER; keys: SET);
  1059.         VAR keysum: SET;
  1060.     BEGIN
  1061.         IF keys = {1} THEN keysum := keys;
  1062.             REPEAT Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y);
  1063.                 Input.Mouse(keys, x, y); keysum := keysum + keys
  1064.             UNTIL keys = {};
  1065.             IF keysum = {1} THEN e.uline := ~e.uline; Changed(e, pos) END
  1066.         END
  1067.     END Track;
  1068.     PROCEDURE Handle (e: Texts.Elem; VAR msg: Texts.ElemMsg);
  1069.         VAR copy: Elem;
  1070.     BEGIN
  1071.         WITH e: Elem DO
  1072.             IF msg IS Texts.CopyMsg THEN
  1073.                 NEW(copy); Copy(e, copy); msg(Texts.CopyMsg).e := copy
  1074.             ELSIF msg IS Texts.IdentifyMsg THEN
  1075.                 WITH msg: Texts.IdentifyMsg DO msg.mod := "FancyElems"; msg.proc := "Alloc" END
  1076.             ELSIF msg IS Texts.FileMsg THEN
  1077.                 WITH msg: Texts.FileMsg DO
  1078.                     IF msg.id = Texts.load THEN Load(e, msg.r)
  1079.                     ELSIF msg.id = Texts.store THEN Store(e, msg.r)
  1080.                     END
  1081.                 END
  1082.             ELSIF msg IS TextFrames.DisplayMsg THEN
  1083.                 WITH msg: TextFrames.DisplayMsg DO
  1084.                     IF msg.prepare THEN PrepDraw(e, msg.fnt, msg.Y0)
  1085.                     ELSE Draw(e, msg.pos, msg.fnt, msg.col, msg.X0, msg.Y0, msg.elemFrame)
  1086.                     END
  1087.                 END
  1088.             ELSIF msg IS TextPrinter.PrintMsg THEN
  1089.                 WITH msg: TextPrinter.PrintMsg DO
  1090.                     IF msg.prepare THEN PrepPrint(e, msg.fnt, msg.Y0)
  1091.                     ELSE Print(e, msg.pos, msg.fnt, msg.X0, msg.Y0)
  1092.                     END
  1093.                 END
  1094.             ELSIF msg IS TextFrames.TrackMsg THEN
  1095.                 WITH msg: TextFrames.TrackMsg DO Track(e, msg.pos, msg.X, msg.Y, msg.keys) END
  1096.             END
  1097.         END
  1098.     END Handle;
  1099.     PROCEDURE Insert*;
  1100.         VAR e: Elem; s: Texts.Scanner; M: TextFrames.InsertElemMsg;
  1101.     BEGIN Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(s);
  1102.         IF (s.class = Texts.String) & (s.line = 0) THEN
  1103.             NEW(e); Open(e, s.s, TRUE);
  1104.             M.e := e; Oberon.FocusViewer.handle(Oberon.FocusViewer, M);
  1105.         END
  1106.     END Insert;
  1107. BEGIN
  1108.     NEW(task); task.safe := FALSE; task.handle := Ticker; next := Oberon.Time() + 100;
  1109.     Oberon.Install(task)
  1110. END FancyElems.
  1111.